﻿using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Xml.Linq;

namespace DynamicGeometry
{
    public class DrawingHost : Grid
    {
        public DrawingHost()
        {
            this.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
            this.RowDefinitions.Add(new RowDefinition());

            Behavior.NewBehaviorCreated += Behavior_NewBehaviorCreated;

            CreateRibbon();
            CreateCanvas();
            CreatePropertyGrid();
            CreateStatusBar();

            this.Children.Add(Ribbon);
            this.Children.Add(Canvas);
            this.Children.Add(propertyGridScrollViewer);
            this.Children.Add(StatusBar);

            Grid.SetRow(Canvas, 1);
            Grid.SetRow(propertyGridScrollViewer, 1);
            Grid.SetRow(StatusBar, 1);
                
            Settings.Instance.ShowGrid = true;
            Settings.Instance.EnableOrtho = true;
            Settings.Instance.EnableSnapToGrid = true;

            
            


            CommandNew = new Command(Clear, null, "New", "Drawing");
            CommandUndo = new Command(Undo, null, "Undo", "Drawing");
            CommandRedo = new Command(Redo, null, "Redo", "Drawing");
            CommandToggleGrid = new Command(ToggleGrid, CartesianGrid.GetIcon(), "Grid", "Coordinates");
            CommandToggleOrtho = new Command(ToggleOrtho, new CheckBox(), "Ortho", "Points");
            CommandToggleSnapToGrid = new Command(ToggleSnapToGrid, new CheckBox(), "Snap to grid", "Points");
            CommandToggleSnapToPoint = new Command(ToggleSnapToPoint, new CheckBox(), "Snap to point", "Points");
        }

        public Command CommandNew { get; set; }
        public Command CommandUndo { get; set; }
        public Command CommandRedo { get; set; }

        public Command CommandToggleGrid { get; set; }
        public Command CommandToggleOrtho { get; set; }
        public Command CommandToggleSnapToGrid { get; set; }
        public Command CommandToggleSnapToPoint { get; set; }

        private void CreateStatusBar()
        {
            StatusBar = new StatusBar(this);
            StatusBar.HorizontalAlignment = HorizontalAlignment.Left;
            StatusBar.VerticalAlignment = VerticalAlignment.Bottom;
            Canvas.SetZIndex(StatusBar, (int)ZOrder.StatusBar);
        }

        public Ribbon Ribbon { get; set; }
        public Canvas Canvas { get; set; }
        public PropertyGrid PropertyGrid { get; set; }
        public StatusBar StatusBar { get; set; }

        private void CreatePropertyGrid()
        {
            propertyGridScrollViewer = new ScrollViewer()
            {
                HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
                VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
                Margin = new Thickness(8),
                HorizontalAlignment = HorizontalAlignment.Right,
                VerticalAlignment = VerticalAlignment.Top,
                MinWidth = 200.0,
                Background = new SolidColorBrush(Color.FromArgb(255, 255, 255, 233)),
                Visibility = Visibility.Collapsed
            };

            PropertyGrid = new PropertyGrid();
            PropertyGrid.Margin = new Thickness(4);
            propertyGridScrollViewer.Content = PropertyGrid;
            PropertyGrid.VisibilityChanged += PropertyGrid_VisibilityChanged;

            Canvas.SetZIndex(propertyGridScrollViewer, (int)ZOrder.StatusBar);

            PropertyGrid.ValueDiscoveryStrategy = new ExcludeByDefaultValueDiscoveryStrategy();
        }

        void PropertyGrid_VisibilityChanged(object sender, EventArgs e)
        {
            propertyGridScrollViewer.Visibility = PropertyGrid.Visibility;
        }

        public void ToggleGrid()
        {
            CurrentDrawing.CoordinateGrid.Visible = !CurrentDrawing.CoordinateGrid.Visible;
            Settings.Instance.ShowGrid = CurrentDrawing.CoordinateGrid.Visible;
        }

        public void ToggleOrtho()
        {
            Settings.Instance.EnableOrtho = !Settings.Instance.EnableOrtho;
        }

        public void ToggleSnapToGrid()
        {
            Settings.Instance.EnableSnapToGrid = !Settings.Instance.EnableSnapToGrid;
        }

        public void ToggleSnapToPoint()
        {
            Settings.Instance.EnableSnapToPoint = !Settings.Instance.EnableSnapToPoint;
        }

        ScrollViewer propertyGridScrollViewer;

        private void CreateCanvas()
        {
            Canvas = new Canvas();
            Canvas.Background = new SolidColorBrush(Colors.White);
            Canvas.HorizontalAlignment = HorizontalAlignment.Stretch;
            Canvas.VerticalAlignment = VerticalAlignment.Stretch;

            Canvas.SizeChanged += Canvas_SizeChanged;
        }

        public event EventHandler ReadyForInteraction;

        void Canvas_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (CurrentDrawing == null)
            {
                CurrentDrawing = new Drawing(Canvas);
                Canvas.SizeChanged -= Canvas_SizeChanged;
                if (ReadyForInteraction != null)
                {
                    ReadyForInteraction(this, null);
                }
                HideHint();
            }
        }

        public BehaviorToolButton AddToolButton(Behavior behavior)
        {
            return Ribbon.AddToolButton(behavior);
        }

        void Behavior_NewBehaviorCreated(Behavior behavior)
        {
            AddToolButton(behavior);
        }

        public void CreateRibbon()
        {
            Ribbon = new Ribbon(this);
            
            foreach (var behavior in Behavior.Singletons)
            {
                if (behavior is PolygonCreator || behavior is Dragger || behavior is AreaMeasurementCreator || behavior is DistanceMeasurementCreator)
                {
                    AddToolButton(behavior);
                    
                }
            }
        }

        Drawing mCurrentDrawing;
        public Drawing CurrentDrawing
        {
            get
            {
                return mCurrentDrawing;
            }
            set
            {
                if (mCurrentDrawing != null)
                {
                    mCurrentDrawing.ConstructionStepStarted -= mCurrentDrawing_ConstructionStepStarted;
                    mCurrentDrawing.ConstructionStepComplete -= mCurrentDrawing_ConstructionStepComplete;
                    mCurrentDrawing.ActionManager.CollectionChanged -= ActionManager_CollectionChanged;
                    mCurrentDrawing.Status -= mCurrentDrawing_Status;
                    mCurrentDrawing.SelectionChanged -= mCurrentDrawing_SelectionChanged;
                    mCurrentDrawing.BehaviorChanged -= mCurrentDrawing_BehaviorChanged;
                    mCurrentDrawing.DocumentOpenRequested -= mCurrentDrawing_DocumentOpenRequested;
                    mCurrentDrawing.DisplayProperties -= mCurrentDrawing_DisplayProperties;
                    mCurrentDrawing.UnhandledException -= mCurrentDrawing_UnhandledException;
                    mCurrentDrawing.Canvas = null;
                    PropertyGrid.Show(null, null);
                }
                mCurrentDrawing = value;
                if (mCurrentDrawing != null)
                {
                    mCurrentDrawing.ActionManager.CollectionChanged += ActionManager_CollectionChanged;
                    mCurrentDrawing.ConstructionStepStarted += mCurrentDrawing_ConstructionStepStarted;
                    mCurrentDrawing.ConstructionStepComplete += mCurrentDrawing_ConstructionStepComplete;
                    mCurrentDrawing.Status += mCurrentDrawing_Status;
                    mCurrentDrawing.SelectionChanged += mCurrentDrawing_SelectionChanged;
                    mCurrentDrawing.BehaviorChanged += mCurrentDrawing_BehaviorChanged;
                    mCurrentDrawing.DocumentOpenRequested += mCurrentDrawing_DocumentOpenRequested;
                    mCurrentDrawing.DisplayProperties += mCurrentDrawing_DisplayProperties;
                    mCurrentDrawing.UnhandledException += mCurrentDrawing_UnhandledException;
                    mCurrentDrawing.SetDefaultBehavior();
                }
                UpdateUndoRedo();
            }
        }

        public event EventHandler<UnhandledExceptionNotificationEventArgs> UnhandledException;

        void mCurrentDrawing_UnhandledException(object sender, UnhandledExceptionNotificationEventArgs args)
        {
            if (UnhandledException != null)
            {
                UnhandledException(sender, args);
            }
        }

        public void HandleException(Exception ex)
        {
            mCurrentDrawing_UnhandledException(this, new UnhandledExceptionNotificationEventArgs(ex));
        }

        public void Clear()
        {
            CurrentDrawing = new Drawing(Canvas);
        }

        void mCurrentDrawing_DisplayProperties(object sender, Drawing.DisplayPropertiesEventArgs e)
        {
            ShowProperties(e.Object);
        }

        void mCurrentDrawing_DocumentOpenRequested(object sender, Drawing.DocumentOpenRequestedEventArgs e)
        {
            LoadDrawing(e.DocumentXml);
        }

        void mCurrentDrawing_BehaviorChanged(Behavior newBehavior)
        {
            Ribbon.SelectBehavior(newBehavior);
            var help = newBehavior.HintText;
            if (!help.IsEmpty())
            {
                ShowHint(help);
            }
            ShowProperties(newBehavior.PropertyBag);
        }

        void mCurrentDrawing_SelectionChanged(object sender, Drawing.SelectionChangedEventArgs e)
        {
            var selection = e.SelectedFigures
                    .Where(f => f != null && f.Visible)
                    .FirstOrDefault();
            ShowProperties(selection);
        }

        void mCurrentDrawing_Status(string obj)
        {
            ShowHint(obj);
        }

        public bool ConstructionInProgress { get; set; }

        void mCurrentDrawing_ConstructionStepStarted(object sender, Drawing.ConstructionStepStartedEventArgs e)
        {
            ConstructionInProgress = true;
            UpdateUndoRedo();
        }

        void mCurrentDrawing_ConstructionStepComplete(object sender, Drawing.ConstructionStepCompleteEventArgs args)
        {
            if (args.ConstructionComplete)
            {
                ConstructionInProgress = false;
                UpdateUndoRedo();
                HideHint();
            }
            else
            {
                CommandRedo.Enabled = false;
                UpdateHint(args);
            }
        }

        private void UpdateHint(Drawing.ConstructionStepCompleteEventArgs args)
        {
            string expectedFigure = "";
            if (args.FigureTypeNeeded.HasInterface<IPoint>())
            {
                expectedFigure = "point";
            }
            else if (args.FigureTypeNeeded.HasInterface<ILine>())
            {
                expectedFigure = "line, ray or a segment";
            }
            else if (args.FigureTypeNeeded.HasInterface<ICircle>())
            {
                expectedFigure = "circle";
            }
            else if (args.FigureTypeNeeded.HasInterface<ILinearFigure>())
            {
                expectedFigure = "line or a circle";
            }
            else
            {
                expectedFigure = args.FigureTypeNeeded.Name;
            }
            string hint = string.Format("Click a {0}.", expectedFigure);
            ShowHint(hint);
        }

        public void LoadDrawing(string drawingXml)
        {
            XElement xml = null;
            try
            {
                xml = XElement.Parse(drawingXml);
            }
            catch (Exception ex)
            {
                ShowHint("Invalid file format: " + ex.ToString());
                return;
            }
            LoadDrawing(xml);
        }

        public void LoadDrawing(XElement element)
        {
            try
            {
                Clear();
                CurrentDrawing.AddFromXml(element);
                HideHint();
            }
            catch (Exception ex)
            {
                CurrentDrawing.RaiseError(this, ex);
            }
        }

        public void LoadDrawingFromDGF(string[] lines)
        {
            try
            {
                ShowOperationDuration(() =>
                {
                    Clear();
                    CurrentDrawing.AddFromDGF(lines);
                });
            }
            catch (Exception ex)
            {
                CurrentDrawing.RaiseError(this, ex);
            }
        }

        public void ShowHint(string text)
        {
            if (text.IsEmpty())
            {
                StatusBar.Visibility = Visibility.Collapsed;
            }
            else
            {
                StatusBar.Text = text;
                StatusBar.Visibility = Visibility.Visible;
            }
        }

        public void ShowOperationDuration(Action code)
        {
            var duration = Utilities.ElapsedTime(code);
            ShowHint(string.Format("Processed in {0} milliseconds", duration));
        }

        public void HideHint()
        {
            StatusBar.Visibility = Visibility.Collapsed;
        }

        void ShowSelectionProperties()
        {
            var selection = CurrentDrawing.GetSelectedFigures();
            var single = selection.FirstOrDefault();
            ShowProperties(single);
        }

        public void ShowProperties(object selection)
        {
            try
            {
                PropertyGrid.Show(selection, CurrentDrawing.ActionManager);
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        public void Undo()
        {
            if (ConstructionInProgress)
            {
                CurrentDrawing.Behavior.Cancel();
                return;
            }
            try
            {
                CurrentDrawing.ActionManager.Undo();
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
            UpdateUndoRedo();
            ShowProperties(null);
        }

        public void Redo()
        {
            try
            {
                CurrentDrawing.ActionManager.Redo();
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
            UpdateUndoRedo();
            ShowProperties(null);
        }

        private void UpdateUndoRedo()
        {
            try
            {
                CommandUndo.Enabled = CurrentDrawing.ActionManager.CanUndo || ConstructionInProgress;
                CommandRedo.Enabled = CurrentDrawing.ActionManager.CanRedo;
                CommandNew.Enabled = CurrentDrawing.Figures.Count > 0;
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        void ActionManager_CollectionChanged(object sender, EventArgs e)
        {
            UpdateUndoRedo();
        }

        public bool ShowCoordinateGrid
        {
            get
            {
                if (CurrentDrawing == null || CurrentDrawing.CoordinateGrid == null)
                {
                    return DynamicGeometry.Settings.Instance.ShowGrid;
                }
                return CurrentDrawing.CoordinateGrid.Visible;
            }
            set
            {
                CurrentDrawing.CoordinateGrid.Visible = value;
                DynamicGeometry.Settings.Instance.ShowGrid = value;
            }
        }

        public CommandToolButton AddToolbarButton(Command command)
        {
            return Ribbon.AddToolButton(command);
        }
    }
}
